package cn.xuqiudong.snippet.shiro;

import org.apache.shiro.session.Session;
import org.apache.shiro.session.UnknownSessionException;
import org.apache.shiro.session.mgt.ValidatingSession;
import org.apache.shiro.session.mgt.eis.EnterpriseCacheSessionDAO;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.io.Serializable;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * 描述:
 *   重写shiro的sessionDao， 基于redis的TTL 自动过期策略
 *   并把session存放与request 减少读取redis的次数 （已经在 SessionManager  做了这个操作 可省略）
 * @author Vic.xu
 * @since 2025-05-30 15:17
 */
public class RedisSessionDao extends EnterpriseCacheSessionDAO {

    private static final String SESSION_REDIS_KEY_PREFIX = "shiro:session:";

    public static final long  DEFAULT_SESSION_TIMEOUT_MINUTES = 4 * 60L;

    private long timeOut = DEFAULT_SESSION_TIMEOUT_MINUTES;

    private RedisTemplate<String, Object> redisTemplate;

    public void setTimeOut(long timeOut) {
        this.timeOut = timeOut;
    }

    public void setRedisTemplate(RedisTemplate<String, Object> redisTemplate) {
        this.redisTemplate = redisTemplate;
    }

    @Override
    protected Serializable doCreate(Session session) {
        Serializable sessionId = generateSessionId(session);
        assignSessionId(session, sessionId);
        redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session, timeOut, TimeUnit.MINUTES);
        return sessionId;
    }

    @Override
    public void update(Session session) throws UnknownSessionException {
        if (session instanceof ValidatingSession && !((ValidatingSession)session).isValid()) {
            return;
        }
        redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + session.getId().toString()).set(session, timeOut, TimeUnit.MINUTES);
    }

    @Override
    public void delete(Session session) {
        // 或者考虑更新其失效时间为0?
        redisTemplate.delete(SESSION_REDIS_KEY_PREFIX + session.getId().toString());
        HttpServletRequest request = getRequest();
        // 一定要进行空值判断，因为SessionValidationScheduler的线程也会调用这个方法，而在那个线程中是不存在Request对象的
        if (request != null) {
            request.removeAttribute(session.getId().toString());
        }
    }

    @Override
    protected Session doReadSession(Serializable sessionId) {
        HttpServletRequest request = getRequest();
        if (request != null) {
            Session sessionObj = (Session) request.getAttribute(sessionId.toString());
            if (sessionObj != null) {
                return sessionObj;
            }
        }

        Session session = (Session) redisTemplate.boundValueOps(SESSION_REDIS_KEY_PREFIX + sessionId).get();
        if (session != null && request != null) {
            request.setAttribute(sessionId.toString(), session);
        }
        return session;
    }

    @Override
    public Collection<Session> getActiveSessions() {
        Set<String> keys = redisTemplate.keys(SESSION_REDIS_KEY_PREFIX + "*");
        if (keys != null && !keys.isEmpty()) {
            List<Object> sessions = redisTemplate.opsForValue().multiGet(keys);
            if (sessions != null) {
                return sessions.stream().map(o -> (Session) o).collect(Collectors.toList());
            }
        }
        return Collections.emptySet();
    }

    private HttpServletRequest getRequest() {
        try {
            ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
            return requestAttributes != null ? requestAttributes.getRequest() : null;
        } catch (Exception e) {
            //忽略异常 直接返回null
            return null;
        }
    }

}
